package ran.gateway.filter;

import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import ran.constants.HttpConstants;
import ran.constants.RedisCacheConstants;
import ran.constants.UserIdentity;
import ran.entity.LoginUser;
import ran.entity.Result;
import ran.enums.ResultCode;
import ran.gateway.properties.IgnoreWhiteProperties;
import ran.redis.service.RedisService;
import ran.utils.JwtUtils;
import reactor.core.publisher.Mono;
import com.alibaba.fastjson2.JSON;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;


import io.jsonwebtoken.Claims;


import java.util.List;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: ran
 * Date: 2025-10-09
 * Time: 17:35
 */
@Slf4j
@Component
// 定义全局拦截器 确保每次调用接口时, 都会拦截下来进行身份验证
public class AuthFilter implements GlobalFilter, Ordered {
    // 排除过滤的 uri 白名单地址，在nacos自行添加
    @Autowired
    private IgnoreWhiteProperties ignoreWhite; // 白名单
    @Value("${jwt.secret}")
    private String secret; // 从nacos获取自定义的密文
    @Autowired
    private RedisService redisService;
    // 过滤器器的主逻辑, 都要经过该过滤逻辑
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String url = request.getURI().getPath();
        // 跳过不需要验证的路径
        if (matches(url, ignoreWhite.getWhites())) {
            return chain.filter(exchange); // 继续下一步逻辑
        }
        //从http请求头中获取token
        String token = getToken(request);
        if (StrUtil.isEmpty(token)) {
            return unauthorizedResponse(exchange, "令牌不能为空");
        }

        Claims claims;
        try {
            claims = JwtUtils.parseToken(token, secret); //获取令牌中信息 解析payload中信息
            if (claims == null) {
                return unauthorizedResponse(exchange, "令牌已过期或验证不正确！");
            }
        } catch (Exception e) {
            return unauthorizedResponse(exchange, "令牌已过期或验证不正确！");
        }

        String userKey = JwtUtils.getUserKey(claims); //获取jwt中的key
        // 获取 key 之后去redis来查看redis中是否过期, 过期的话不管jwt令牌是否过期, 直接返回已过期
        boolean isLogin = redisService.hasKey(getTokenKey(userKey)/* 调用这个getTokenKey方法是因为在Redis中存储的 key 是 固定前缀+jwt中的key */);
        if (!isLogin) {
            return unauthorizedResponse(exchange, "登录状态已过期");
        }

        // 令牌不过期, 那么查看userId是否存在
        String userid = JwtUtils.getUserId(claims); //判断jwt中的信息是否完整
        if (StrUtil.isEmpty(userid)) {
            return unauthorizedResponse(exchange, "令牌验证失败");
        }

        // 从Redis中获取存储的 identity 身份, 我们之前设定的是管理员请求有 /system 前缀, 普通用户有 /friend 前缀
        // 我们这里判断请求 URL 中是否有这个对应请求前缀, 并且判断对应身份与从Redis取出的 identity(1/2) 是否相同
        LoginUser user = redisService.getCacheObject(getTokenKey(userKey), LoginUser.class);
        if (url.contains(HttpConstants.SYSTEM_URL_PREFIX) && !UserIdentity.ADMIN.getValue().equals(user.getIdentity())) {
            return unauthorizedResponse(exchange, "令牌验证失败");
        }
        if (url.contains(HttpConstants.FRIEND_URL_PREFIX) && !UserIdentity.ORDINARY.getValue().equals(user.getIdentity())) {
            return unauthorizedResponse(exchange, "令牌验证失败");
        }

        // 身份验证通过, 继续下一步逻辑
        return chain.filter(exchange);
    }
    /**
     * 查找指定url是否匹配指定匹配规则链表中的任意一个字符串
     *
     * @param url 指定url
     * @param patternList 需要检查的匹配规则链表
     * @return 是否匹配
     */
    private boolean matches(String url, List<String> patternList) {
        if (StrUtil.isEmpty(url) || CollectionUtils.isEmpty(patternList)) {
            return false;
        }
        // 循环判断白名单中是否有和当前URL匹配的
        for (String pattern : patternList) {
            if (isMatch(pattern, url)) {//跳转到主逻辑
                return true;
            }
        }
        return false;
    }
    /**
     * 判断url是否与规则匹配
     * 匹配规则中：
     * ? 表示单个字符;
     * * 表示一层路径内的任意字符串，不可跨层级;
     * ** 表示任意层路径;
     *
     * @param pattern 匹配规则
     * @param url 需要匹配的url
     * @return 是否匹配
     */
    private boolean isMatch(String pattern, String url) {
        AntPathMatcher matcher = new AntPathMatcher();
        return matcher.match(pattern, url); // 判断是否匹配的主逻辑, 可使用匹配规则
    }
    /**
     * 获取缓存key
     */
    private String getTokenKey(String token) {
        return RedisCacheConstants.LOGIN_TOKEN_KEY + token;
    }
    /**
     * 从请求头中获取请求token
     */
    private String getToken(ServerHttpRequest request) {
        String token = request.getHeaders().getFirst(HttpConstants.AUTHENTICATION);
        // 如果前端设置了令牌前缀，则裁剪掉前缀
        if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
        return token;
    }
    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg) {
        log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
        return webFluxResponseWriter(exchange.getResponse(), msg,
                ResultCode.FAILED_UNAUTHORIZED.getCode());
    }
    //拼装webflux模型响应
    private Mono<Void> webFluxResponseWriter(ServerHttpResponse response,
                                             String msg, int code) {
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE,
                MediaType.APPLICATION_JSON_VALUE);
        Result<?> result = Result.fail(code, msg);
        DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONString(result).getBytes());
        return response.writeWith(Mono.just(dataBuffer));
    }
    @Override
    public int getOrder() {
        return -200;
    }
    public static void main(String[] args) {
        AuthFilter authFilter = new AuthFilter();
// 测试 ?
        String pattern = "/sys/?bc";
        System.out.println(authFilter.isMatch(pattern,"/sys/abc"));
        System.out.println(authFilter.isMatch(pattern,"/sys/cbc"));
        System.out.println(authFilter.isMatch(pattern,"/sys/acbc"));
        System.out.println(authFilter.isMatch(pattern,"/sdsa/abc"));
        System.out.println(authFilter.isMatch(pattern,"/sys/abcw"));
    }
}
